package com.bs.gataway.config;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.bs.common.constant.AuthConstant;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author bingshao
 * @date 2022/9/20
 **/
@Component
@Log4j2
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext authorizationContext) {
        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
        String path = request.getURI().getPath();
//        PathMatcher pathMatcher = new AntPathMatcher();
        String roleIds = stringRedisTemplate.opsForValue().get(AuthConstant.USER_ROLE + path);

        // 获取当前url需要的角色权限，可以将url与role的对应关系存入redis，再通过url获取到所需角色id，下面再判断该账号角色有无包含上述redis中所取到的id
        List<String> authorities = StrUtil.isNotBlank(roleIds) ? Arrays.stream(roleIds.split(",")).map(role -> AuthConstant.AUTHORITY_PREFIX + role).collect(Collectors.toList()) : new LinkedList<>();
        // 为空说明没做限制，都能访问
        boolean isPublic = CollectionUtil.isEmpty(authorities);

        Mono<AuthorizationDecision> authorizationDecisionMono = authentication
                .filter(Authentication::isAuthenticated)
                .flatMapIterable(Authentication::getAuthorities)
                .map(GrantedAuthority::getAuthority)
                .any(roleId -> {
                    if (isPublic) {
                        return true;
                    }
                    //  roleId是请求用户的角色(格式:ROLE_{roleId})，authorities是请求资源所需要角色的集合
                    log.info("访问路径：{}", path);
                    log.info("用户角色roleId：{}", roleId);
                    log.info("资源需要权限authorities：{}", authorities);
                    return authorities.contains(roleId);
                })
                .map(AuthorizationDecision::new)
                .defaultIfEmpty(new AuthorizationDecision(false));
        return authorizationDecisionMono;

    }
}
